package fi.bluepill.osm;

import com.mongodb.*;
import org.openstreetmap.osmosis.core.container.v0_6.EntityContainer;
import org.openstreetmap.osmosis.core.domain.v0_6.*;
import org.openstreetmap.osmosis.core.task.v0_6.Sink;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

public class LocationImportPlugin implements Sink {
    private long interestingLocations = 0;
    private DB db;
    private String collection;
    private String mongoURI;

    private double bbLatMin = 200;
    private double bbLatMax;
    private double bbLonMin = 200;
    private double bbLonMax;
    private Date modifiedDate = new Date();

    @Override
    public void process(EntityContainer entityContainer) {
        Entity entity = entityContainer.getEntity();
        boolean interestingEntity = false;

        if (entity.getType() == EntityType.Node) {
            Node node = (Node) entity;
            storeNode(node);
        }

        if (!entity.getTags().isEmpty()) {
            for (Tag tag : entity.getTags()) {
                if (tag.getKey().equals("amenity")) {
                    String val = tag.getValue();

                    if (val.equals("parking") || val.equals("atm") || val.equals("waste_basket") || val.equals("fire_hydrant") || val.equals("bench") || val.equals("taxi")
                            || val.equals("shelter") || val.equals("vending_machine") || val.equals("post_box") || val.equals("car_sharing") || val.equals("waste_disposal")
                            || val.equals("recycling") || val.equals("bicycle_parking") || val.equals("toilets") || val.equals("clock") || val.equals("litter_bin") || val.equals("swimming_pool"))

                        break;

                    interestingEntity = true;
                    break;
                }

                if (tag.getKey().equals("shop")) {
                    interestingEntity = true;
                    break;
                }
            }
        }

        if (interestingEntity) {
            log("Found interesting node");

            if (entity.getType() == EntityType.Node) {
                Node node = (Node)entity;

                if (node.getLatitude() > bbLatMax)
                    bbLatMax = node.getLatitude();

                if (node.getLatitude() < bbLatMin)
                    bbLatMin = node.getLatitude();

                if (node.getLongitude() > bbLonMax)
                    bbLonMax = node.getLongitude();

                if (node.getLongitude() < bbLonMin)
                    bbLonMin = node.getLongitude();
            }

            BasicDBList loc = new BasicDBList();

            if (entity.getType() == EntityType.Node) {
                Node node = (Node)entity;

                loc.add(node.getLatitude());
                loc.add(node.getLongitude());
            }

            else if (entity.getType() == EntityType.Way) {
                Way way = (Way)entity;
                List<WayNode> nodes = way.getWayNodes();

                double latSum = 0, lonSum = 0;

                for (WayNode wayNode : nodes) {
                    Node node = loadNode(wayNode.getNodeId());

                    latSum += node.getLatitude();
                    lonSum += node.getLongitude();
                }

                loc.add(latSum / nodes.size());
                loc.add(lonSum / nodes.size());
            }

            DBCollection col = db.getCollection(collection);

            DBCursor cursor = col.find(new BasicDBObject("locationId", entity.getId()));

            DBObject poi = null;

            if (!cursor.hasNext())
                poi = new BasicDBObject();
            else
                poi = cursor.next();

            poi.put("loc", loc);
            poi.put("creatorUserId", "system");
            poi.put("ownerUserId", "system");
            poi.put("locationId", entity.getId());
            poi.put("version", entity.getVersion());
            poi.put("modified", modifiedDate);

            for (Tag tag : entity.getTags()) {
                if (tag.getKey().startsWith("name") || tag.getKey().equals("amenity") || tag.getKey().equals("shop") || tag.getKey().equals("website"))
                    poi.put(tag.getKey().replaceAll("\\.", "-"), tag.getValue());
            }

            col.save(poi);

            log("Node saved");

            interestingLocations++;
        }
    }

    @Override
    public void initialize(Map<String, Object> stringObjectMap) {
        log("intialize");

        URI mongolabUri = null;

        try {
            mongolabUri = new URI(mongoURI);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        Mongo mongo = null;

        try {
            mongo = new Mongo(new MongoURI(mongoURI));
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }

        String dbName = mongolabUri.getPath().replaceAll("/", "");

        db = mongo.getDB(dbName);

        if (mongolabUri.getUserInfo() != null) {
            String username = mongolabUri.getUserInfo().split(":")[0];
            String password = mongolabUri.getUserInfo().split(":")[1];
            db.authenticate(username, password.toCharArray());
        }

        DBCollection col = db.getCollection(collection + "work");
        col.drop();
    }

    @Override
    public void complete() {
        log("complete, found " + interestingLocations + " interesting locations");

        DBCollection col = db.getCollection(collection);

        BasicDBObject index = new BasicDBObject();
        index.put("loc", "2d");
        index.put("background", true);
        col.ensureIndex(index);

        DBCursor curs = col.find(new BasicDBObject("id", "bbox"));

        DBObject bbox = null;

        if (curs.hasNext())
            bbox = curs.next();
        else
            bbox = new BasicDBObject();

        bbox.put("latMin", bbLatMin);
        bbox.put("latMax", bbLatMax);
        bbox.put("lonMin", bbLonMin);
        bbox.put("lonMax", bbLonMax);
        bbox.put("id", "bbox");

        col.save(bbox);

        log("Bounding box for imported dataset: (" + bbLatMin + "," + bbLonMin + ") - (" + bbLatMax + "," + bbLonMax + ")");
    }

    @Override
    public void release() {
        log("release");

        DBCollection col = db.getCollection(collection + "work");
        col.drop();

    }

    public String getCollection() {
        return collection;
    }

    public void setCollection(String collection) {
        this.collection = collection;
    }

    public String getMongoURI() {
        return mongoURI;
    }

    public void setMongoURI(String mongoURI) {
        this.mongoURI = mongoURI;
    }

    private int batchCounter = 0;
    private List<DBObject> nodeList = new ArrayList<DBObject>();

    private void storeNode(Node node) {
        BasicDBObject nodeJson = new BasicDBObject();
        nodeJson.put("_id", node.getId());
        nodeJson.put("lat", node.getLatitude());
        nodeJson.put("lon", node.getLongitude());


        nodeList.add(nodeJson);
        batchCounter++;

        if (batchCounter == 100000) {
            DBCollection col = db.getCollection(collection + "work");

            col.insert(nodeList);
            nodeList.clear();
            batchCounter = 0;
        }
    }

    private Node loadNode(long nodeId) {
        DBCollection col = db.getCollection(collection + "work");

        if (batchCounter > 0) {
            col.insert(nodeList);
            nodeList.clear();
            batchCounter = 0;
        }

        BasicDBObject criteria = new BasicDBObject();
        criteria.put("_id", nodeId);

        DBObject result = col.findOne(criteria);

        CommonEntityData entityData = new CommonEntityData(1234, 0, new Date(System.currentTimeMillis()), new OsmUser(1, "system"), 1);
        return new Node(entityData, (Double)result.get("lat"), (Double)result.get("lon"));
    }
    
    private void log(String message) {
        
    }
}
